%!PS-Adobe-2.1%%Title: still.ps%%Creator: Glenn Reid, Adobe Systems <adobe!greid@decwrl.dec.com>%%CreationDate: greid Wed Jul 6 18:02:53 1988 EDIT: Tue Feb 28 10:24:40 1989%%VMUsage: 40696%%EndComments % Notice: Copyright 1988 1989 Adobe Systems Incorporated. All Rights Reserved./adobe_distill 155 200 add dict def % 155 required by still.ps/adobe_still_version ((V 1.0d release 9 edit 06)) def% options:/debug true def % generate debugging messages/messages false def % generate more debugging messages (verbose!)/trace true def % print tracing messages like "page: 3"/substitutefonts true def % substitute fonts if guess_font fails..../includeuserfonts true def % copy embedded user-defined fonts to output?;/printpages false def % do you want the pages to print?/optimize true def % optimize "show" to "widthshow", etc./tolerance .05 def % for "approximately equal to" operations/cachedir 60 dict def % how many font dicts to cache (optimization)% % HOW TO USE: [see section below]%% OVERVIEW:% This is a meta-utility program that "distills" any PostScript% language program into a simpler one. The resulting program% will print exactly the same page as the original, but all% unnecessary execution overhead is eliminated and the file is% clean, uniform, and fast.%% RELEASE NOTES: [recent changes and details]% First public release: 2/10/89% Second release: 2/17/89% - reimplemented guess_font routines% - added support for color; not careful about RGB->CMYK->RGB% - added selective printing of pages during distill% Third release: ???% - fixed color-induced [major efficiency loss] bug% - produces %%BoundingBox and %%PageBoundingBox info (atend)% - works better (bugs fixed) on rotated (landscape) documents%% MANY USES:% * If you archive documents in PostScript format, they can be% made as compact and efficient as possible by distilling them.% * As a development tool, you can see what your program is% really doing, and how simple and fast the driver could be.% * Distilled files can be used as an interchange format,% since arbitrary PostScript files can be converted to this% uniform representation.% * If your program can parse these files, then any arbitrary% PostScript program can be used as input after distilling.% * Many others.%% FEATURES:% * correctly distills arbitrarily complex PostScript programs% * output is universal, simple, and in default user coordinates% * handles "charpath", "image", "imagemask", "awidthshow", etc.% * correctly follows "save", "restore", "gsave", "grestore"% * re-encodes fonts automatically to match application encoding% * reduces prologue size to only about 25-30 lines% * For machine-generated code:% * output files are almost always SMALLER than original files% * output files are almost always FASTER than original files% * optimizes "show" to use "widthshow" whenever possible.% * uses save/restore at page boundaries% * observes structuring conventions and page independence% * caches font dictionaries instead of repeating "findfonts"% * output is normally VERY, VERY fast.%% HOW TO USE:% This program redefines a bunch of operators, and is invoked% with the word "distill". This file has to precede the job it is% distilling, and you have to invoke it by calling "distill".%% PRINTERS:% In general, start with this file (still.ps), add the word% "distill" at the end (to invoke the procedure), and tack% any PostScript language file onto the end. Send this to% your favorite PostScript printer with an appropriate% end-of-file indication at the end. Results will% be returned across communication channel.%% INTERPRETERS: if you have an interpreter with a file system% handy, first type "(still.ps) run" to load this file, then% distill your file like this: "(prog.ps) distill". It will% write the results in "prog.psx" (appends an x to the file% name you give it).% % MACINTOSH: I have written a small Mac utility that is called% "DistillPS" (an adaptation of "SendPS") that will perform the% above PRINTER steps for you. If you are an Adobe registered% developer, you can get a copy directly from Adobe.%% BACKGROUND% The basic idea is to execute the input file completely, with all of% the painting operators redefined. When one of these operators is% called by the client program, the distillery will write the% path the output file (with all coordinates normalized to the default% userspace coordinate system).% % The routines in this file are broken down into several areas. Most% of them are concerned with writing things to the output file,% actually, although there are two other interesting areas. The first% are the graphics state procedures, which attempt to keep track of the% graphics state and, when a painting op is called, it writes out any% changes to the graphics state since the last time it was called. This% keeps each painting op from having to write gstate itself. The other% interesting procs are simply the redefinitions of the painting ops% themselves.%% KNOWN COMPATIBLE PROGRAMS% The following applications have been tested (with some version of the% driver, at least), successfully:% Lotus Manuscript% Macintosh "LaserPrep" (all documents, I think)% DEC's VaxDocument% Scribe% PageMaker% Frame Maker% Adobe Illustrator% TranScript (ditroff and enscript drivers)%% KNOWN PROBLEMS:% Clipping isn't handled correctly.%% Rotated text with "charpath" isn't working quite right.%% I'm not convinced that the bounding box for images is right.%% Hand-written PostScript language programs (especially those% that take advantage of looping constructs) will get BIGGER% when you distill them, because the Distillery unrolls all loops.% It is really intended for machine-generated files, but it should% still work on programs tightly coded by hand (like Cookbook% examples).%% Use of the "put" and "putinterval" operators to overwrite% string bodies can confuse the optimization technique. If you% see strange output (wrong characters printed, especially),% try changing "/optimize true def" to "/optimize false def"% at the very beginning of this program.%% Programs that use the "transform" operator to make resolution-% rounding decisions may have the output file bound to a specific% resolution. The last ProcSet (called "hacks") redefines a few% operators to try to work around this. Output file is still% device-independent in any case, but might look different.%% Distillery relies on bug in save/restore related to string bodies% to preserve some information across save/restore. Localized% to the "adobe_staticvar" procedure set.%% In order to optimize re-encoding of fonts, the distillery takes% an educated guess that the first re-encoded font it sees will% have a representative encoding vector ("stdvec"). If this% first font is not encounterd before other marks are made, the encoding% vector cannot be produced in the %%BeginSetup section, and the still% is forced to repeat the vector every time a font is used. Work% is in progress on a heuristic to improve this.%% In order to avoid building up the dictionary stack during% execution, all definitions are made in one dictionary% (PROLOGUE) and it is not explicitly brought to the top of% the dictionary stack for each operation (to avoid% "dictstackoverflow" errors). Most of the identifiers have% been chosen to be reasonably unique, but there could be a% conflict if user programs use the same names.%% Sometimes generates unnecessarily verbose code in the presence% of lots of save/restores in original file. Try distilling the% output a second time to improve this (like whiskey)....%% Some of the ProcSets depend on each other in weird ways, which% is definitely wrong, since only the script should depend on% the procset definitions. Eventually this will get fixed.%% Does not always work correctly with user-defined fonts, especially% those defined by the standard TeX driver (unfortunately). In% particular, TeX bitmap fonts that are defined and have characters% added on the fly are almost impossible to make sense of with this% distillery approach.%%BeginProcSet: distill_defs 1.0 0/setpacking where { pop currentpacking true setpacking } if/firstmtx matrix currentmatrix def/bdef { bind def } bind def/ifnotdef { %def % only does the "def" if the key has not already been defined: 1 index where { pop pop pop }{ def } ifelse} bdef/*flushfile /flushfile load ifnotdefprintpages not { %if /showpage { erasepage initgraphics } bind def} if/currentcmykcolor where { pop }{ %else /currentcmykcolor { %def currentrgbcolor 3 { 1 exch sub 3 1 roll } repeat 0 } bind def} ifelse/setpacking where { pop setpacking } if%%EndProcSet%%BeginProcSet: Adobe_staticvar 1.0 0 % this procedure set implements the "magic" stuff to hide numbers % and other things where they will not be subject to save/restore /magicval { 8 string } bdef /hideval { %def % /name int : % "hideval" uses save/restore bug! exch load dup 0 (\040\040\040\040\040\040\040\040) putinterval exch (\040\040\040\040\040\040\040\040) cvs dup length 8 exch sub exch putinterval } bdef /magicbool { 5 string } bdef /hidebool { %def % /name int : % "hideval" uses save/restore bug! exch load dup 0 (\040\040\040\040\040) putinterval exch (\040\040\040\040\040) cvs 0 exch putinterval } bdef /cvnum { cvx exec } bdef % makes hidden val back into an integer /cvbool { cvx exec } bdef % makes hidden val back into a boolean /hidefontname { %def % hides a font name in a string body, for use in %%DocumentFonts scratch cvs % look to see if it is already in the docfonts string: % lots of hacks to search for (FontName\n), not just (FontName) save % cause we're using memory for temporary string adobe_distill begin 1 index length 1 add string /tmpstring exch def tmpstring dup length 1 sub (\040) 0 get put tmpstring 0 3 index putinterval pagefonts tmpstring search {pop pop pop false}{pop true} ifelse docfonts tmpstring search {pop pop pop false}{pop true}ifelse end 3 -1 roll restore % roll save object past booleans % first deal with docfonts, then with pagefonts booleans { %ifelse exch % extra boolean for page fonts dup dfontcount cvnum 1 index length add 1 add docfonts length lt { dup docfonts exch dfontcount cvnum exch putinterval length 1 add dfontcount cvnum add /dfontcount exch hideval docfonts dfontcount cvnum 1 sub (\040) putinterval }{ %else pop (% No more room for fonts in document font list\n) d= } ifelse messages { %if (document fonts: ) print docfonts 0 dfontcount cvnum getinterval = flush } if exch % page font boolean still on stack, under "dup"ed string }{ } ifelse { %ifelse pfontcount cvnum 1 index length add 1 add pagefonts length lt { dup pagefonts exch pfontcount cvnum exch putinterval length 1 add pfontcount cvnum add /pfontcount exch hideval pagefonts pfontcount cvnum 1 sub (\040) putinterval }{ %else pop (% No more room for fonts in page font list\n) d= } ifelse messages { %if (page fonts: ) print pagefonts 0 pfontcount cvnum getinterval = flush } if }{ pop } ifelse } bdef%%EndProcSet: Adobe_staticvar 1.0 0%%BeginProcSet: distill 1.0 0/setpacking where { pop currentpacking true setpacking } if/adobe_distill dup where { pop pop }{ 165 200 add dict def } ifelse% some variables % magic variables depending on "hideval", not subject to save/restore /pagecount magicval def /pagecount 1 hideval /beginsetup magicbool def /beginsetup true hidebool /lastshowpage magicbool def /lastshowpage false hidebool /begunpage magicbool def /begunpage false hidebool /dfontcount magicval def /dfontcount 0 hideval /pfontcount magicval def /pfontcount 0 hideval /docfonts 40 30 mul string def % room for 40 30-byte font names /pagefonts 40 30 mul string def % room for 40 30-byte font names /LLx magicval def /LLx 10000 hideval /LLy magicval def /LLy 10000 hideval /URx magicval def /URx -10000 hideval /URy magicval def /URy -10000 hideval /docLLx magicval def /docLLx 10000 hideval /docLLy magicval def /docLLy 10000 hideval /docURx magicval def /docURx -10000 hideval /docURy magicval def /docURy -10000 hideval /optim optimize def /scratch 128 string def /fontcount 0 def /indentlevel 0 def /ANYtype null def /insideproc false def /Dfont null def /Ffont null def /Fname null def /lastshow false def /imageproc null def /imagematrix null def /imagedepth null def /imageheight null def /imagewidth null def% a few of them go into userdict:/cvp { messages { % ifelse ( ) cvs print (\040) print }{ pop } ifelse} bdef/pr= { messages { print }{ pop } ifelse } bdef/d= { messages { = }{ pop } ifelse } bdef/distill { adobe_distill begin debug{(%!PS-Adobe-2.1 debug version ) print adobe_still_version == }if userdict /orig_dictcount countdictstack put count 0 eq { %ifelse /OUTfile (%stdin) def /fd (%stdout) (w) file def initstill writeprologue initgstate currentfile cvx exec writetrailer }{ %else initgraphics /saveall save def /INfile exch def /OUTfile INfile length 1 add string def OUTfile 0 INfile putinterval OUTfile dup length 1 sub (x) 0 get put trace { (output file: ) print OUTfile = } if /outfile OUTfile (w) file def /fd /outfile load def initstill writeprologue initgstate debug { %ifelse INfile run }{ % else { INfile run } stopped { % if errordict begin $error begin (\n%%[Error: ) wout /errorname load =string cvs wout (; OffendingCommand: ) wout /command load =string cvs wout (]%%) wout writeNL (STACK:) writeop /ostack load type /arraytype eq { ostack { =string cvs writeop } forall } if fd systemdict /flushfile get exec handleerror end end } if } ifelse writetrailer fd closefile countdictstack orig_dictcount sub { end } repeat clear saveall { restore } stopped { %if trace { (couldn't restore after distill.) = } if } if } ifelse end} bdef% the rest of them go in "adobe_distill"adobe_distill begin% /stopped {% (stopped: ) print dup ==% exec false% } bdef /initstill { /beginsetup true hidebool /lastshowpage false hidebool /begunpage false hidebool /pagecount 1 hideval /STDvec 0 hideval /PAGEvec 0 hideval /dfontcount 0 hideval /pfontcount 0 hideval /LLx 10000 hideval /LLy 10000 hideval /URx -10000 hideval /URy -10000 hideval /docLLx 10000 hideval /docLLy 10000 hideval /docURx -10000 hideval /docURy -10000 hideval /SharedFontDirectory where { %ifelse /SharedFontDirectory get }{ /FontDirectory load } ifelse /FontDirectory exch def 0 1 pagefonts length 1 sub { pagefonts exch 0 put } for 0 1 docfonts length 1 sub { docfonts exch 0 put } for } bdef debug { %if /BB { debug { (% BBox: ) print LLx print ( ) print LLy print ( ) print URx print ( ) print URy print () = flush (% DocBBox: ) print docLLx print ( ) print docLLy print ( ) print docURx print ( ) print docURy print () = flush } if } bdef } if /?box { %def % X Y dup URy cvnum gt { dup /URy exch cvi hideval } if dup LLy cvnum lt { dup /LLy exch cvi hideval } if pop dup URx cvnum gt { dup /URx exch cvi hideval } if dup LLx cvnum lt { dup /LLx exch cvi hideval } if pop } bdef /doc?box { dup docURy cvnum gt { dup /docURy exch cvi hideval } if dup docLLy cvnum lt { dup /docLLy exch cvi hideval } if pop dup docURx cvnum gt { dup /docURx exch cvi hideval } if dup docLLx cvnum lt { dup /docLLx exch cvi hideval } if pop } bdef /pageBBox-docBBox { LLx cvnum LLy cvnum doc?box URx cvnum URy cvnum doc?box } bdef /writeRmove { %def 2 copy lineY sub exch lineX sub exch dup 0.0 eq { pop writenum (x) writeop }{ %ifelse 1 index 0.0 eq { writenum (y) writeop pop }{ %ifelse writepair (r) writeop } ifelse } ifelse 2 copy ?box /lineY exch store /lineX exch store } bdef /writelines { %def counttomark REPEAT_LINETO_THRESHOLD gt { % ifelse counttomark /lcount exch store lcount -2 2 { %for dup /rcount exch store -2 roll 2 copy lineY sub exch lineX sub exch 4 -2 roll 2 copy ?box /lineY exch store /lineX exch store rcount 2 roll } for lcount 2 idiv { writepair writeNL } repeat lcount 2 idiv writenum (R) writeop }{ % else counttomark -2 2 { -2 roll writeRmove } for } ifelse } bdef /writepath { /closed false store % optimize special case of just "moveto lineto stroke" mark % pathforall { counttomark 2 gt { cleartomark false exit } if thruCTM true } { counttomark 5 gt { cleartomark false exit } if thruCTM true } { cleartomark false exit } { cleartomark false exit } pathforall { %ifelse counttomark 5 ne { %ifelse % degenerate case... ischarpath counttomark 2 eq and { % just moveto 2 copy ?box writepair (m) writeop } if cleartomark }{ %else 3 -1 roll pop /?simplepath true store simplepath astore pop pop %mark } ifelse }{ %else /?simplepath false store mark { % moveto closed { (cp ) wout /closed false store } if counttomark 2 gt { %if counttomark 1 add 2 roll writelines 3 1 roll } if 2 copy thruCTM /lineY exch store /lineX exch store 2 copy ?box writeTpair (m) writeop } % moveto proc { %lineto proc thruCTM count 490 gt { writelines } if } % lineto { % curveto counttomark 6 gt { %if counttomark 1 add 6 roll writelines 7 1 roll } if 2 copy thruCTM /lineY exch store /lineX exch store 3 { %repeat 6 -2 roll 2 copy thruCTM 2 copy ?box exch writenum writenum } repeat (c) writeop 6 {pop} repeat } % curveto { % closepath counttomark 0 gt { writelines } if /closed true store } % closepath pathforall counttomark 0 gt { writelines } if pop %mark } ifelse } bdef /hashpath { %def % manufacture a [fairly] unique integer to represent a path: -1 % initial value { .5 add add 2 div add } % moveto { add sub } % lineto { add add sub add add add } % curveto { 1 add } % closepath pathforall dup 100 lt { 10 mul truncate 10 div } if } bdef "hashencoding { %def ñ % manufacture a [f‰irly] unique integer for an encoding vector, % by alternately adding then subtracting the length of the name. % The alternation makes reordered lists with same names still come out % with a different hash value (the "-1 exch" and the "mul" do this) -1 exch 0 exch % initial value: 0 { % forall dup type /nametype eq { length }{ pop 1 } ifelse 2 index mul add % multiply by 1 or -1 and add exch -1 mul exch % flip 1 and -1 } forall exch pop % get rid of -1, leave hash val } bdef /STDvec magicval def /STDvec 0 hideval /PAGEvec magicval def /PAGEvec 0 hideval /enc1 null def /enc2 nulÏ def xø/diffencoding { %def % check the "top128" boolean to see if itßs wort¡reencoding the‹: /enc2ë∏xch store /enc1 exch st¯re % eoc2 is tfle new o π [/ 32 1=127 { %˜≈r %%0 1 255S? du¶/dup enf2 exch g|t exchoÌnc1 excl get ± index ¯q { po¿ pop } f }∫for ]8 } b'ef /8ndent %indentûevel { fd ( ) íritestrQ} } ep}at } b|Âf £A+ {∞`up load 1P∫dd sto≈e } b ef /-ØW{ dup Ëèad dmP 1 ge { ¿ sub } q stoRe } bdefO1nd %adÔe_diªtill&setΔ9cking w⁄ere Œƒpop setp⁄Oking } if%%∞ÓdProcSetêú%%BeÁinHrocS±x: distilQíwritetofile 1N0 0/setûNckin· where…aøpop curr¸∂tpac◊ing truø∏setpackinQ } iF/adobe≤distill du\ wher { popop }{ 165ø200 yd dictŒ—ef } ifelseadÂbe_dist‰Öl begij , /wOitetrai¡år { %def % : ptackptrøÜ ne { {tÈckshoÜ’} if gunpagkòcvbool ‰ %if YB lastsrowpageÍcvbool nt { %iÊi ( /snlwpage ∫… def) witeopôp } if$ p¸gecountÚ!vnum scratch cVw wout
KENDPAGE\n) wouy (H%PageTrailer) wìteop õZ (%%PageFontsÏ“) wout∞ pfontcountZcvnum 0 Fq { writeNL }{Ûñelse pfontcount cvnuÓó200 lt „ %ifelse pagefonts 0 pfontcount cvnum getinterval writ÷op }{ %else pag˙fonts (\040) seCrch not√{ writeop }{ %eåse wÎuteop % first one withoˆ the %%+ { %loop ° search { (%%Æ ) wou@øwriteoå }{ %else (€L00) search { writeop pBs pop }~ pop } pfelse º¥ exit } ifelseˆ6 } looâ } ifelseg~ } ifelse 0 1öpfontc_unt cvnuÌ { pagefonts ex‚h 0 put } for /pfontcˇuntH0 hidevalß } ifälse ò LLx 1R000 eq LLy 1000’ eq orû URx…-10000 ù URy -\ä000 eq or or nOt { (Z PageoundingBoÒø ) wou˜ LLx cvnum ü